package plp1;

import java.util.ArrayList;
import java.util.HashMap;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author eltorre
 */
@SuppressWarnings("unchecked")
public class ASintactico {

	private boolean hayMain = false;
	private ALexico alexico;
	private MyToken preanalisis;
	private MyToken anterior;
	HashMap<String, ArrayList<LanzarRegla>> baseReglas;
	TablaSimbolos tablaSimbolos = new TablaSimbolos();

	public ASintactico (ALexico lex)
	{
		alexico = lex;
		ArrayList reglas;
		ArrayList<LanzarRegla> lr;

		baseReglas = new HashMap<String, ArrayList<LanzarRegla>>();

		// S	C S¹ {S⁰.t = C.t + S¹.t}
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();
		reglas.add("C");
		reglas.add("S");
		reglas.add(0);	//C.t
		reglas.add(1);	//S¹.t
		lr.add(new LanzarRegla(new MyToken(MyToken.CLASS), reglas));

		// {S.t = “”}

		reglas = new ArrayList();

		lr.add(new LanzarRegla(new MyToken(MyToken.EOF), reglas));

		baseReglas.put("S", lr);

		// C -> class id llavei D llaved {TablaSimbolos.put(id, clase)} }{D.h = “    “} D {C.t = class.lexema + id.lexema + llavei.lexema + TablaSimbolos.toString() + D.t + llaved.lexema + “\n”}
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add("addTab");
		reglas.add(new MyToken(MyToken.CLASS));
		reglas.add(new MyToken(MyToken.ID));
		reglas.add("TablaSimbolos.put()");  //
		reglas.add(1);						// Clase a tablasimbolo.
		reglas.add(Simbolo.CLASE);			//
		reglas.add(0); //Deberia ignorarse	//
		reglas.add("TablaSimbolos.push()"); //push
		reglas.add(new MyToken(MyToken.LLAVEI));
		reglas.add("D");
		reglas.add(new MyToken(MyToken.LLAVED));
		reglas.add(0); //class
		reglas.add(1); //id
		reglas.add(2); //{
		reglas.add("TablaSimbolos.pop()"); //identificadores
		reglas.add(3); //D
		reglas.add(4); //}
		lr.add(new LanzarRegla(new MyToken(MyToken.CLASS), reglas));

		baseReglas.put("C", lr);

		// D
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add("E");
		reglas.add("D");
		reglas.add(0); //E
		reglas.add(1); //D

		lr.add(new LanzarRegla(new MyToken(MyToken.DOUBLE), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.INT), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.VOID), reglas));

		reglas = new ArrayList();

		lr.add(new LanzarRegla(new MyToken(MyToken.LLAVED), reglas));

		baseReglas.put("D", lr);

		// E
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add("addTab");
		reglas.add("V");
		lr.add(new LanzarRegla(new MyToken(MyToken.DOUBLE), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.INT), reglas));

		reglas = new ArrayList();

		reglas.add("addTab");
		reglas.add("M");
		lr.add(new LanzarRegla(new MyToken(MyToken.VOID), reglas));

		baseReglas.put("E", lr);

		// V
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add(new MyToken(MyToken.DOUBLE));
		reglas.add(new MyToken(MyToken.ID));
		reglas.add("TablaSimbolos.put()"); //
		reglas.add(1); //id
		reglas.add(Simbolo.VAR); //variable
		reglas.add(Simbolo.REAL); //double
		reglas.add(new MyToken(MyToken.PYC));
		lr.add(new LanzarRegla(new MyToken(MyToken.DOUBLE), reglas));

		reglas = new ArrayList();

		reglas.add(new MyToken(MyToken.INT));
		reglas.add(new MyToken(MyToken.ID));
		reglas.add("TablaSimbolos.put()"); //
		reglas.add(1); //id
		reglas.add(Simbolo.VAR); //variable
		reglas.add(Simbolo.ENTERO); //int
		reglas.add(new MyToken(MyToken.PYC));
		lr.add(new LanzarRegla(new MyToken(MyToken.INT), reglas));

		baseReglas.put("V", lr);

		// M
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add(new MyToken(MyToken.VOID));
		reglas.add("MID");
		reglas.add("TablaSimbolos.put()"); //
		reglas.add(1); //id
		reglas.add(Simbolo.METODO); //variable
		reglas.add(Simbolo.ENTERO); //int
		reglas.add(new MyToken(MyToken.PARI));
		reglas.add(new MyToken(MyToken.PARD));
		reglas.add(new MyToken(MyToken.LLAVEI));
		reglas.add("TablaSimbolos.push()"); //push
		reglas.add("DECL");
		reglas.add("CUERPO");
		reglas.add(new MyToken(MyToken.LLAVED));
		reglas.add(0); //void
		reglas.add(1); //Mid
		reglas.add(2); //(
		reglas.add(3); //)
		reglas.add(4); //{
		reglas.add("TablaSimbolos.pop()"); //pop
		reglas.add(6); //Cuerpo
		reglas.add("minusTab");
		reglas.add("h");
		reglas.add(7); //}
		reglas.add("TablaSibolos.addMetodo()");
		reglas.add(1);
		lr.add(new LanzarRegla(new MyToken(MyToken.VOID), reglas));

		baseReglas.put("M", lr);

		// MID

		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();
		reglas.add(new MyToken(MyToken.MAIN));
		reglas.add(0);
		reglas.add("HayMain");
		lr.add(new LanzarRegla(new MyToken(MyToken.MAIN), reglas));

		reglas = new ArrayList();
		reglas.add(new MyToken(MyToken.ID));
		reglas.add(0);
		lr.add(new LanzarRegla(new MyToken(MyToken.ID), reglas));

		baseReglas.put("MID", lr);

		// DECL
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add("V");
		reglas.add("DECL");
		//lr.add(new LanzarRegla(new Token(Token.INT), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.DOUBLE), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.INT), reglas));

		reglas = new ArrayList();

		//lr.add(new LanzarRegla(new Token(Token.ID), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.LLAVED), reglas));
		lr.add(new LanzarRegla(new MyToken(MyToken.ID), reglas));

		baseReglas.put("DECL", lr);

		// CUERPO
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();
		lr.add(new LanzarRegla(new MyToken(MyToken.LLAVED), reglas));
	
		reglas = new ArrayList();
		reglas.add("INSTR");
		reglas.add("CUERPO");
		reglas.add("h"); //tabulador
		reglas.add(0); //instruccion
		reglas.add(1); //cuerpo
		lr.add(new LanzarRegla(new MyToken(MyToken.ID), reglas));

		baseReglas.put("CUERPO", lr);

		// INSTR
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add("addTab");
		reglas.add(new MyToken(MyToken.ID));
		reglas.add("Usada()");
		reglas.add(0);
		reglas.add("ASIG");
		reglas.add(new MyToken(MyToken.PYC));
		reglas.add(0); //id
		reglas.add(1); //ASIG
		reglas.add(2); //;

		lr.add(new LanzarRegla(new MyToken(MyToken.ID), reglas));

		baseReglas.put("INSTR", lr);

		// ASIG
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();
		reglas.add("checkFuncion()");
		reglas.add(new MyToken(MyToken.PARI));
		reglas.add(new MyToken(MyToken.PARD));
		reglas.add(0); //(
		reglas.add(1); //)
		lr.add(new LanzarRegla(new MyToken(MyToken.PARI), reglas));

		reglas = new ArrayList();
		reglas.add("checkVariable()");
		reglas.add(new MyToken(MyToken.ASIG));
		reglas.add("FACTOR");
		reglas.add(0); //=
		reglas.add(1); //factor
		lr.add(new LanzarRegla(new MyToken(MyToken.ASIG), reglas));

		baseReglas.put("ASIG", lr);

		// FACTOR
		lr = new ArrayList<LanzarRegla>();

		reglas = new ArrayList();

		reglas.add(new MyToken(MyToken.ENTERO));
		reglas.add(0);
		lr.add(new LanzarRegla(new MyToken(MyToken.ENTERO), reglas));

		reglas = new ArrayList();

		reglas.add(new MyToken(MyToken.ID));
		reglas.add(0);
		reglas.add("Usada()");
		reglas.add(0);
		reglas.add("checkVariableFactor()");
		lr.add(new LanzarRegla(new MyToken(MyToken.ID), reglas));

		reglas = new ArrayList();

		reglas.add(new MyToken(MyToken.REAL));
		reglas.add(0);
		lr.add(new LanzarRegla(new MyToken(MyToken.REAL), reglas));


		baseReglas.put("FACTOR", lr);

	}

	public String emparejar(MyToken t) throws Excepcion
	{
		String aux;
		if (t.equals(preanalisis))
		{
			anterior = preanalisis;
			aux = preanalisis.getLexema();
			//System.out.println(preanalisis);
			preanalisis = alexico.nextToken();
			//System.out.println("Tratamos " + preanalisis);
			return aux;
		}
		else
		{
			if (preanalisis.isEof())
			{
				throw new Excepcion("Error 4: encontrado final de fichero, esperaba " + t.getError());
			}
			throw new Excepcion ("Error 3 (" + preanalisis.getFila() + "," + preanalisis.getColumna() + "): encontrado " +
					preanalisis.getError() + ", esperaba " + t.getError());
		}
	}

	public String master(String actual, String h) throws Excepcion
	{
		ArrayList<LanzarRegla> r = baseReglas.get(actual);
		ArrayList<String> trozos = new ArrayList<String>();
		LanzarRegla lr;
		String sintetizado = "";
		String cadena;
		boolean b = false;
		Simbolo simboloAux;
		int i, j = 0;

		//System.out.println("Bajamos a " + actual);
		//for (LanzarRegla lr : r)
		for (i = 0; !b && i < r.size(); i++)	//Cuando lanzamos una regla dejamos de chequear el resto
		{
			lr = r.get(i);
			if (lr.prediccion.equals(preanalisis))
			{
				b = true;
				for (j = 0; j < lr.reglas.size(); j++)		//No pueden ser bucles separados, tiene que ser el mismo
				{
					if (lr.reglas.get(j).getClass() == MyToken.class)
					{
						trozos.add(emparejar((MyToken)lr.reglas.get(j)));
					}

					else if(lr.reglas.get(j).getClass() == Integer.class)
					{
						sintetizado += trozos.get((Integer)lr.reglas.get(j));
					}

					else if (((String)lr.reglas.get(j)).equals("h"))
					{
						sintetizado += h;
					}
					else if(((String) lr.reglas.get(j)).equals("HayMain"))
					{
						hayMain = true;
					}

					else if(((String) lr.reglas.get(j)).equals("TablaSimbolos.pop()"))
					{
						sintetizado +=  tablaSimbolos.pop(h);
					}
					else if(((String) lr.reglas.get(j)).equals("TablaSimbolos.push()"))
					{
						tablaSimbolos.push();
					}
					else if(((String) lr.reglas.get(j)).equals("TablaSimbolos.put()"))
					{
						if (tablaSimbolos.existe(trozos.get((Integer)lr.reglas.get(j+1))))
						{
							throw new Excepcion ("Error 5 (" + anterior.getFila() + "," + anterior.getColumna() +
								"): '" + trozos.get((Integer)lr.reglas.get(j+1)) + "' ya existe en este ambito");
							
						}
						tablaSimbolos.nuevoSimbolo( trozos.get((Integer)lr.reglas.get(j+1)), (Integer)lr.reglas.get(j+2), (Integer)lr.reglas.get(j+3));
						j = j+3;
					}
					else if(((String) lr.reglas.get(j)).equals("Usada()"))
					{
						simboloAux = tablaSimbolos.busca(trozos.get((Integer)lr.reglas.get(j+1)));

						if (simboloAux != null)
						{
							simboloAux.setUsado();
						}
						else
						{
							throw new Excepcion("Error 6 (" + anterior.getFila() + "," + anterior.getColumna() +
									"): '" + trozos.get((Integer)lr.reglas.get(j+1)) + "' no ha sido declarado");
						}
						j++;
					}
					else if(((String) lr.reglas.get(j)).equals("Main?") && !hayMain)
					{
						throw new Excepcion("No hay main");
					}
					else if (((String) lr.reglas.get(j)).equals("TablaSibolos.addMetodo()"))
					{
						if (tablaSimbolos.existe(trozos.get((Integer)lr.reglas.get(j+1))))
						{
							tablaSimbolos.busca(trozos.get((Integer)lr.reglas.get(j+1))).setCuerpo(sintetizado);
						}
						else
						{
							throw new Excepcion ("WTF");
						}
					}
					else if (((String) lr.reglas.get(j)).equals("addTab"))
					{
						h += "    ";
					}
					else if (((String) lr.reglas.get(j)).equals("minusTab"))
					{
						h = h.substring(4);
					}
					else if (((String) lr.reglas.get(j)).equals("checkVariable()"))
					{
						simboloAux = tablaSimbolos.busca(h);
						if (simboloAux.tipoSimbolo != Simbolo.VAR)
						{
							throw new Excepcion("Error 7 (" + anterior.getFila() + "," + anterior.getColumna() +
									"): '" + h + "' no es una variable");
						}
					}
					else if (((String) lr.reglas.get(j)).equals("checkVariableFactor()"))
					{
						simboloAux = tablaSimbolos.busca(anterior.getLexema());
						if (simboloAux.tipoSimbolo != Simbolo.VAR)
						{
							throw new Excepcion("Error 7 (" + anterior.getFila() + "," + anterior.getColumna() +
									"): '" + anterior.getLexema() + "' no es una variable");
						}
					}
					else if (((String) lr.reglas.get(j)).equals("checkFuncion()"))
					{
						simboloAux = tablaSimbolos.busca(h);
						if (simboloAux.tipoSimbolo != Simbolo.METODO)
						{
							throw new Excepcion("Error 8 (" + anterior.getFila() + "," + anterior.getColumna() +
									"): '" + h + "' no es un metodo");
						}
					}
					else
					{
						if (actual.equals("INSTR"))
						{
							trozos.add(master((String)lr.reglas.get(j), trozos.get(0)));
						}
						/*if (((String)lr.reglas.get(j)).equals("C")  || ((String)lr.reglas.get(j)).equals("M"))
						{
							trozos.add(master((String)lr.reglas.get(j), h + actual ));
						}*/
						else
						{
							trozos.add(master((String) lr.reglas.get(j), h));
						}
					}
				}
			}
		}
		if (!b)
		{
			if (preanalisis.isEof())
			{
				cadena = "Error 4: encontrado final de fichero, esperaba";
			}
			else
			{
				cadena = "Error 3 (" + preanalisis.getFila() + "," + preanalisis.getColumna() + "): encontrado ";
				cadena += preanalisis.getError() + ", esperaba";
			}
			for (i = 0; !b && i < r.size(); i++)
			{
				lr = r.get(i);
				cadena += " ";
				cadena += lr.prediccion.getError();
			}
			throw new Excepcion (cadena);

		}
		if (preanalisis == null)
		{
			throw (new Excepcion("Error 2. Se encontro " + preanalisis.toString() + "\n \t\tSe esperaba " + preanalisis.toString()));
		}

		return sintetizado;
	}

	public String Lanzar() throws Excepcion
	{
		String ret;
		preanalisis = alexico.nextToken();
		ret = master("S", "");
		if (!alexico.nextToken().equals(new MyToken(MyToken.EOF)))
		{
			emparejar(new MyToken(MyToken.EOF));
		}
		if(hayMain == false)
		{
			throw new Excepcion("Error 9: 'main' no esta definido");
		}

		return ret;
	}
}
